#include <btl/Image/Image.hpp>

#include <boost/smart_ptr.hpp>
#include <boost/test/unit_test.hpp>
#include <memory>

struct RawImageDataFixture
{
   RawImageDataFixture():
      w(16), h(8),
      data(new unsigned char[w*h])
   {

      // fill rows with successive values
      for(int i = 0; i < h; ++i)
         std::fill(&data[w * i], &data[w * (i + 1)], i);
   }

   const int w;
   const int h;
   boost::scoped_array<unsigned char> data;
};

BOOST_AUTO_TEST_SUITE(Image_Image)

BOOST_FIXTURE_TEST_CASE(region_construction, RawImageDataFixture)
{
   using namespace btl::image;

   ImageRegion<unsigned char> r1;
   BOOST_CHECK(r1.null());
   BOOST_CHECK_EQUAL(0, r1.width());
   BOOST_CHECK_EQUAL(0, r1.height());

   const ImageRegion<const unsigned char> r2(data.get(), w, h);
   BOOST_CHECK(!r2.null());
   BOOST_CHECK_EQUAL(w, r2.width());
   BOOST_CHECK_EQUAL(h, r2.height());

   ImageRegion<unsigned char> r3(data.get(), w, h);
   BOOST_CHECK(!r3.null());
   BOOST_CHECK_EQUAL(w, r3.width());
   BOOST_CHECK_EQUAL(h, r3.height());

   const ImageRegion<const unsigned char> r4(r2);
   const ImageRegion<const unsigned char> r5(r3);
   //ImageRegion<unsigned char> r6(r2); // shouldn't compile
   ImageRegion<unsigned char> r6(r3);
}

BOOST_FIXTURE_TEST_CASE(region_const_access, RawImageDataFixture)
{
   using namespace btl::image;

   const ImageRegion<const unsigned char> r(data.get(), w, h);
   BOOST_CHECK_EQUAL(0, r(0, 0));
   BOOST_CHECK_EQUAL(0, r(0, 1));
   BOOST_CHECK_EQUAL(0, r(0, w - 1));
   BOOST_CHECK_EQUAL(1, r(1, 0));
   BOOST_CHECK_EQUAL(1, r(1, w - 1));
   BOOST_CHECK_EQUAL(h - 1, r(h - 1, 0));
   BOOST_CHECK_EQUAL(h - 1, r(h - 1, 1));
   BOOST_CHECK_EQUAL(h - 1, r(h - 1, w - 1));
}

BOOST_FIXTURE_TEST_CASE(region_strided_access, RawImageDataFixture)
{
   using namespace btl::image;

   const ImageRegion<const unsigned char> r(data.get(), w / 2, h / 2, sizeof(unsigned char)*w);
   BOOST_CHECK(!r.null());
   BOOST_CHECK_EQUAL(w / 2, r.width());
   BOOST_CHECK_EQUAL(h / 2, r.height());

   const int maxi = h / 2 - 1;
   const int maxj = w / 2 - 1;

   BOOST_CHECK_EQUAL(0, r(0, 0));
   BOOST_CHECK_EQUAL(0, r(0, maxj));
   BOOST_CHECK_EQUAL(1, r(1, 0));
   BOOST_CHECK_EQUAL(1, r(1, maxj));
   BOOST_CHECK_EQUAL(maxi, r(maxi, 0));
   BOOST_CHECK_EQUAL(maxi, r(maxi, maxj));
}

BOOST_FIXTURE_TEST_CASE(region_offset_access, RawImageDataFixture)
{
   using namespace btl::image;

   const int offseti = 3;
   const int offsetj = 6;
   const int maxi = h - offseti - 1;
   const int maxj = w - offsetj - 1;
   const ImageRegion<const unsigned char> r(&data[w * offseti + offsetj], maxj + 1, maxi + 1, sizeof(unsigned char)*w);
   BOOST_CHECK(!r.null());
   BOOST_CHECK_EQUAL(maxj + 1, r.width());
   BOOST_CHECK_EQUAL(maxi + 1, r.height());

   BOOST_CHECK_EQUAL(offseti + 0, r(0, 0));
   BOOST_CHECK_EQUAL(offseti + 0, r(0, maxj));
   BOOST_CHECK_EQUAL(offseti + 1, r(1, 0));
   BOOST_CHECK_EQUAL(offseti + 1, r(1, maxj));
   BOOST_CHECK_EQUAL(h - 1, r(maxi, 0));
   BOOST_CHECK_EQUAL(h - 1, r(maxi, maxj));
}

BOOST_FIXTURE_TEST_CASE(region_write_access, RawImageDataFixture)
{
   using namespace btl::image;

   ImageRegion<unsigned char> r(data.get(), w, h);
   // check write-access
   r(0, 0) = 255; // first row should now be 255,0,0,0,...
   r(1, 0) = 255; // second row should be    255,1,1,1,...
   BOOST_CHECK_EQUAL(255, data[0 * w + 0]);
   BOOST_CHECK_EQUAL(0, data[0 * w + 1]);
   BOOST_CHECK_EQUAL(255, data[1 * w + 0]);
   BOOST_CHECK_EQUAL(1, data[1 * w + 1]);
}

BOOST_FIXTURE_TEST_CASE(region_access_pointers, RawImageDataFixture)
{
   using namespace btl::image;

   ImageRegion<unsigned char> r(data.get(), w, h);

   // check pointer equivalence and one-past-the-end access
   BOOST_CHECK_EQUAL(&data[0], &r(0, 0));
   BOOST_CHECK_EQUAL(&data[0], r[0]);
   BOOST_CHECK_EQUAL(&data[w - 1], &r(0, w - 1));
   BOOST_CHECK_EQUAL(&data[w], &r(0, w));
   BOOST_CHECK_EQUAL(&data[w], r[1]);

   BOOST_CHECK_EQUAL(&data[0], r.data());
}

BOOST_AUTO_TEST_CASE(image_construct_null)
{
   using namespace btl::image;

   Image<unsigned char> im;
   BOOST_CHECK(im.null());
   BOOST_CHECK_EQUAL(0, im.width());
   BOOST_CHECK_EQUAL(0, im.height());
}

BOOST_AUTO_TEST_CASE(image_construct_sized)
{
   using namespace btl::image;
   const int w = 9;
   const int h = 13;
   Image<unsigned char> im(w, h);
   BOOST_CHECK(!im.null());
   BOOST_CHECK_EQUAL(w, im.width());
   BOOST_CHECK_EQUAL(h, im.height());

   int idx = 0;
   for(int i = 0; i < h; ++i)
      for(int j = 0; j < w; ++j)
         im(i, j) = idx++;
   BOOST_REQUIRE_EQUAL(w * h, idx);

   BOOST_CHECK_EQUAL(0, im(0, 0));
   BOOST_CHECK_EQUAL(1, im(0, 1));
   BOOST_CHECK_EQUAL(w - 1, im(0, w - 1));
   BOOST_CHECK_EQUAL(w, im(1, 0));
   BOOST_CHECK_EQUAL(w * h - 1, im(h - 1, w - 1));
}

BOOST_AUTO_TEST_CASE(image_construct_sized_and_filled)
{
   using namespace btl::image;
   const int w = 9;
   const int h = 13;
   Image<unsigned char> im(w, h, 42);
   BOOST_CHECK(!im.null());
   BOOST_CHECK_EQUAL(w, im.width());
   BOOST_CHECK_EQUAL(h, im.height());

   BOOST_CHECK_EQUAL(42, im(0, 0));
   BOOST_CHECK_EQUAL(42, im(0, 1));
   BOOST_CHECK_EQUAL(42, im(0, w - 1));
   BOOST_CHECK_EQUAL(42, im(1, 0));
   BOOST_CHECK_EQUAL(42, im(h - 1, w - 1));
}

BOOST_FIXTURE_TEST_CASE(image_construct_from_region, RawImageDataFixture)
{
   using namespace btl::image;

   const ImageRegion<const unsigned char> r(data.get(), w, h);
   Image<unsigned char> im(r);
   BOOST_CHECK(!im.null());
   BOOST_CHECK_EQUAL(w, im.width());
   BOOST_CHECK_EQUAL(h, im.height());

   BOOST_CHECK_EQUAL(0, im(0, 0));
   BOOST_CHECK_EQUAL(0, im(0, 1));
   BOOST_CHECK_EQUAL(0, im(0, w - 1));
   BOOST_CHECK_EQUAL(1, im(1, 0));
   BOOST_CHECK_EQUAL(1, im(1, w - 1));
   BOOST_CHECK_EQUAL(h - 1, im(h - 1, 0));
   BOOST_CHECK_EQUAL(h - 1, im(h - 1, 1));
   BOOST_CHECK_EQUAL(h - 1, im(h - 1, w - 1));
}

BOOST_FIXTURE_TEST_CASE(image_write_access, RawImageDataFixture)
{
   using namespace btl::image;

   const ImageRegion<const unsigned char> r(data.get(), w, h);
   Image<unsigned char> im(r);
   BOOST_CHECK(!im.null());
   BOOST_CHECK_EQUAL(w, im.width());
   BOOST_CHECK_EQUAL(h, im.height());

   // check that a cloned image is independent of its original data
   BOOST_CHECK_EQUAL(0, im(0, 0));
   im(0, 0) = 42;
   BOOST_CHECK_EQUAL(42, im(0, 0));
   BOOST_CHECK_EQUAL(0, data[0]);
   data[0] = 24;
   BOOST_CHECK_EQUAL(42, im(0, 0));
   BOOST_CHECK_EQUAL(24, data[0]);
}

BOOST_FIXTURE_TEST_CASE(image_clone, RawImageDataFixture)
{
   using namespace btl::image;

   const ImageRegion<const unsigned char> r(data.get(), w, h);
   Image<unsigned char> im1(r);

   //Image<unsigned char> im2(im1); // shouldn't compile
   Image<unsigned char> im2;
   im2.clone(im1);

   for(int i = 0; i < h; ++i)
      for(int j = 0; j < w; ++j)
         BOOST_CHECK_EQUAL(im1(i, j), im2(i, j));

   im1(1, 1) = 42;
   im2(1, 1) = 24;
   BOOST_CHECK_EQUAL(42, im1(1, 1));
   BOOST_CHECK_EQUAL(24, im2(1, 1));
}

BOOST_FIXTURE_TEST_CASE(image_reset, RawImageDataFixture)
{
   using namespace btl::image;
   const ImageRegion<const unsigned char> r(data.get(), w, h);

   Image<unsigned char> im(r);
   BOOST_CHECK(!im.null());
   BOOST_CHECK_EQUAL(w, im.width());
   BOOST_CHECK_EQUAL(h, im.height());

   im.reset();
   BOOST_CHECK(im.null());
   BOOST_CHECK_EQUAL(0, im.width());
   BOOST_CHECK_EQUAL(0, im.height());
}

#ifndef BOOST_NO_RVALUE_REFERENCES
BOOST_FIXTURE_TEST_CASE(image_move_construct, RawImageDataFixture)
{
   using namespace btl::image;

   BOOST_TEST_MESSAGE("Testing btl::Image move-construction...");

   const ImageRegion<const unsigned char> r(data.get(), w, h);
   Image<unsigned char> im1(r);
   BOOST_CHECK(!im1.null());
   BOOST_CHECK_EQUAL(w, im1.width());
   BOOST_CHECK_EQUAL(h, im1.height());

   Image<unsigned char> im2(std::move(im1));
   BOOST_CHECK(im1.null());
   BOOST_CHECK_EQUAL(0, im1.width());
   BOOST_CHECK_EQUAL(0, im1.height());
   BOOST_CHECK(!im2.null());
   BOOST_CHECK_EQUAL(w, im2.width());
   BOOST_CHECK_EQUAL(h, im2.height());
}

BOOST_FIXTURE_TEST_CASE(image_move_assign, RawImageDataFixture)
{
   using namespace btl::image;

   BOOST_TEST_MESSAGE("Testing btl::Image move-assignment...");

   const ImageRegion<const unsigned char> r(data.get(), w, h);
   Image<unsigned char> im1(r);
   BOOST_CHECK(!im1.null());
   BOOST_CHECK_EQUAL(w, im1.width());
   BOOST_CHECK_EQUAL(h, im1.height());

   Image<unsigned char> im2;
   BOOST_CHECK(im2.null());
   BOOST_CHECK_EQUAL(0, im2.width());
   BOOST_CHECK_EQUAL(0, im2.height());

   im2 = std::move(im1);

   BOOST_CHECK(im1.null());
   BOOST_CHECK_EQUAL(0, im1.width());
   BOOST_CHECK_EQUAL(0, im1.height());
   BOOST_CHECK(!im2.null());
   BOOST_CHECK_EQUAL(w, im2.width());
   BOOST_CHECK_EQUAL(h, im2.height());
}
#else
BOOST_AUTO_TEST_CASE(no_rvalue_references)
{
   BOOST_TEST_MESSAGE("No support for rvalue-references found. Cannot test Image move-semantics.");
   BOOST_CHECK(true);
}
#endif

BOOST_AUTO_TEST_CASE(smallimage_construct_null)
{
   using namespace btl::image;

   static const int w = 8;
   static const int h = 8;
   SmallImage<unsigned char, w, h> patch;
   BOOST_CHECK(!patch.null());
   BOOST_CHECK_EQUAL(w, patch.width());
   BOOST_CHECK_EQUAL(h, patch.height());
}

BOOST_AUTO_TEST_CASE(smallimage_construct_filled)
{
   using namespace btl::image;

   static const int w = 8;
   static const int h = 8;
   SmallImage<unsigned char, w, h> patch(42);
   BOOST_CHECK(!patch.null());
   BOOST_CHECK_EQUAL(w, patch.width());
   BOOST_CHECK_EQUAL(h, patch.height());

   for(int i = 0; i < h; ++i)
      for(int j = 0; j < w; ++j)
         BOOST_CHECK_EQUAL(42, patch(i, j));
}

BOOST_AUTO_TEST_CASE(smallimage_copy)
{
   using namespace btl::image;

   static const int w = 8;
   static const int h = 8;
   SmallImage<unsigned char, w, h> patch1(42);
   SmallImage<unsigned char, w, h> patch2 = patch1;

   for(int i = 0; i < h; ++i)
      for(int j = 0; j < w; ++j)
         BOOST_CHECK_EQUAL(42, patch2(i, j));

   patch1(0, 0) = 255;
   patch2(0, 0) = 0;
   BOOST_CHECK_EQUAL(255, patch1(0, 0));
   BOOST_CHECK_EQUAL(0, patch2(0, 0));
}

BOOST_AUTO_TEST_CASE(smallimage_assign)
{
   using namespace btl::image;

   static const int w = 8;
   static const int h = 8;
   SmallImage<unsigned char, w, h> patch1(42);
   SmallImage<unsigned char, w, h> patch2(0);

   BOOST_CHECK_EQUAL(0, patch2(0, 0));
   patch2 = patch1;

   for(int i = 0; i < h; ++i)
      for(int j = 0; j < w; ++j)
         BOOST_CHECK_EQUAL(42, patch2(i, j));

   patch1(0, 0) = 255;
   patch2(0, 0) = 0;
   BOOST_CHECK_EQUAL(255, patch1(0, 0));
   BOOST_CHECK_EQUAL(0, patch2(0, 0));
}

BOOST_FIXTURE_TEST_CASE(smallimage_assign_from_region, RawImageDataFixture)
{
   using namespace btl::image;

   const ImageRegion<const unsigned char> r(data.get(), w, h);

   static const int w = 4;
   static const int h = 4;
   static const int offseti = 2;
   static const int offsetj = 4;
   SmallImage<unsigned char, w, h> patch(255);

   BOOST_CHECK_EQUAL(255, patch(0, 0));
   BOOST_CHECK_EQUAL(255, patch(w - 1, h - 1));

   patch = r.region(offsetj, offseti, w, h);

   for(int i = 0; i < h; ++i)
      for(int j = 0; j < w; ++j)
         BOOST_CHECK_EQUAL(i + offseti, patch(i, j));
}

BOOST_FIXTURE_TEST_CASE(smallimage_to_region, RawImageDataFixture)
{
   using namespace btl::image;

   const ImageRegion<const unsigned char> r1(data.get(), w, h);

   static const int w = 4;
   static const int h = 4;
   static const int offseti = 2;
   static const int offsetj = 4;
   SmallImage<unsigned char, w, h> patch = r1.region(offsetj, offseti, w, h);

   const ImageRegion<const unsigned char> r2 = patch;

   for(int i = 0; i < h; ++i)
      for(int j = 0; j < w; ++j)
         BOOST_CHECK_EQUAL(i + offseti, r2(i, j));
}

#if 0 // disable SharedImage tests

BOOST_AUTO_TEST_CASE(sharedimage_construct_null)
{
   using namespace btl::image;

   SharedImage<unsigned char> im;
   BOOST_CHECK(im.null());
   BOOST_CHECK_EQUAL(0, im.width());
   BOOST_CHECK_EQUAL(0, im.height());
   BOOST_CHECK(!im.unique());
}

BOOST_AUTO_TEST_CASE(sharedimage_construct_sized)
{
   using namespace btl::image;

   const int w = 9;
   const int h = 13;
   SharedImage<unsigned char> im(w, h);
   BOOST_CHECK(!im.null());
   BOOST_CHECK_EQUAL(w, im.width());
   BOOST_CHECK_EQUAL(h, im.height());
   BOOST_CHECK(im.unique());

   int idx = 0;
   for(int i = 0; i < h; ++i)
      for(int j = 0; j < w; ++j)
         im(i, j) = idx++;
   BOOST_REQUIRE_EQUAL(w * h, idx);

   BOOST_CHECK_EQUAL(0, im(0, 0));
   BOOST_CHECK_EQUAL(1, im(0, 1));
   BOOST_CHECK_EQUAL(w - 1, im(0, w - 1));
   BOOST_CHECK_EQUAL(w, im(1, 0));
   BOOST_CHECK_EQUAL(w * h - 1, im(h - 1, w - 1));
}

BOOST_AUTO_TEST_CASE(sharedimage_construct_sized_and_filled)
{
   using namespace btl::image;
   const int w = 9;
   const int h = 13;

   SharedImage<unsigned char> im(w, h, 42);
   BOOST_CHECK(!im.null());
   BOOST_CHECK_EQUAL(w, im.width());
   BOOST_CHECK_EQUAL(h, im.height());
   BOOST_CHECK(im.unique());

   BOOST_CHECK_EQUAL(42, im(0, 0));
   BOOST_CHECK_EQUAL(42, im(0, 1));
   BOOST_CHECK_EQUAL(42, im(0, w - 1));
   BOOST_CHECK_EQUAL(42, im(1, 0));
   BOOST_CHECK_EQUAL(42, im(h - 1, w - 1));
}

BOOST_FIXTURE_TEST_CASE(sharedimage_construct_from_region, RawImageDataFixture)
{
   using namespace btl::image;

   const ImageRegion<const unsigned char> r(data.get(), w, h);
   SharedImage<unsigned char> im(r);
   BOOST_CHECK(!im.null());
   BOOST_CHECK_EQUAL(w, im.width());
   BOOST_CHECK_EQUAL(h, im.height());
   BOOST_CHECK(im.unique());

   BOOST_CHECK_EQUAL(0, im(0, 0));
   BOOST_CHECK_EQUAL(0, im(0, 1));
   BOOST_CHECK_EQUAL(0, im(0, w - 1));
   BOOST_CHECK_EQUAL(1, im(1, 0));
   BOOST_CHECK_EQUAL(1, im(1, w - 1));
   BOOST_CHECK_EQUAL(h - 1, im(h - 1, 0));
   BOOST_CHECK_EQUAL(h - 1, im(h - 1, 1));
   BOOST_CHECK_EQUAL(h - 1, im(h - 1, w - 1));
}

BOOST_FIXTURE_TEST_CASE(sharedimage_write_access, RawImageDataFixture)
{
   using namespace btl::image;

   const ImageRegion<const unsigned char> r(data.get(), w, h);
   SharedImage<unsigned char> im(r);
   BOOST_CHECK(!im.null());
   BOOST_CHECK_EQUAL(w, im.width());
   BOOST_CHECK_EQUAL(h, im.height());
   BOOST_CHECK(im.unique());

   // check that a cloned image is independent of its original data
   BOOST_CHECK_EQUAL(0, im(0, 0));
   im(0, 0) = 42;
   BOOST_CHECK_EQUAL(42, im(0, 0));
   BOOST_CHECK_EQUAL(0, data[0]);
   data[0] = 24;
   BOOST_CHECK_EQUAL(42, im(0, 0));
   BOOST_CHECK_EQUAL(24, data[0]);
}

BOOST_FIXTURE_TEST_CASE(sharedimage_clone, RawImageDataFixture)
{
   using namespace btl::image;

   const ImageRegion<const unsigned char> r(data.get(), w, h);
   SharedImage<unsigned char> im1(r);
   BOOST_CHECK(im1.unique());

   SharedImage<unsigned char> im2;
   im2.clone(im1);

   BOOST_CHECK(im1.unique());
   BOOST_CHECK(im2.unique());

   for(int i = 0; i < h; ++i)
      for(int j = 0; j < w; ++j)
         BOOST_CHECK_EQUAL(im1(i, j), im2(i, j));

   im1(1, 1) = 42;
   im2(1, 1) = 24;
   BOOST_CHECK_EQUAL(42, im1(1, 1));
   BOOST_CHECK_EQUAL(24, im2(1, 1));
}

BOOST_FIXTURE_TEST_CASE(sharedimage_reset, RawImageDataFixture)
{
   using namespace btl::image;
   const ImageRegion<const unsigned char> r(data.get(), w, h);

   SharedImage<unsigned char> im(r);
   BOOST_CHECK(!im.null());
   BOOST_CHECK_EQUAL(w, im.width());
   BOOST_CHECK_EQUAL(h, im.height());
   BOOST_CHECK(im.unique());

   im.reset();
   BOOST_CHECK(im.null());
   BOOST_CHECK_EQUAL(0, im.width());
   BOOST_CHECK_EQUAL(0, im.height());
   BOOST_CHECK(!im.unique());
}

BOOST_FIXTURE_TEST_CASE(sharedimage_share, RawImageDataFixture)
{
   using namespace btl::image;

   const ImageRegion<const unsigned char> r(data.get(), w, h);
   SharedImage<unsigned char> im1(r);
   BOOST_CHECK(im1.unique());

   SharedImage<unsigned char> im2;
   BOOST_CHECK(im1.unique());
   BOOST_CHECK(!im2.unique());

   BOOST_CHECK(im2.null());
   im2 = im1;

   BOOST_CHECK(!im1.unique());
   BOOST_CHECK(!im1.unique());
   BOOST_CHECK_EQUAL(im1.data(), im2.data());

   for(int i = 0; i < h; ++i)
      for(int j = 0; j < w; ++j)
         BOOST_CHECK_EQUAL(im1(i, j), im2(i, j));

   im1(1, 1) = 42;
   im2(1, 1) = 24;
   // data is shared, so both should have the second value
   BOOST_CHECK_EQUAL(24, im1(1, 1));
   BOOST_CHECK_EQUAL(24, im2(1, 1));
}

#endif // end disabled SharedImage tests

BOOST_AUTO_TEST_SUITE_END()
